<?php 

	/** 
	 * As the task of uploading thousands of e-mails to Google Mail is going to 
	 * take HOURS / DAYS and grey hairs we try to speed things up a little. 
	 * 
	 * Batch processing allows us to group several e-mails into 
	 * a single post, reducing the number of network transactions required.
	   * 
	 * Google specifies 32Mb as the upload limit, however it seems to choke
	 * on uploads over 15Mb. 
	 * 
	 * A single batch can potentially contain hundreds of small messages, but again 
	 * if the number is too large Google will not accept it so we cap it at 20.
	 * 
	 * @author 		Martijn Dijksterhuis
	 * @copyright 	2009
	 * @license 	GPL v2.0
	 * @version		$id$ 
	 */

	/**
	 * This class collects several e-mails and when flush() is called they
	 * are put together into an XML structure and uploaded to Google
	 */

	class BatchUploader
	{
		const   GOOGLEBATCH_LIMIT = 10000000; // Not quite 10Mb, 10-15Mb should be ok
		const	GOOGLEBATCH_URL   = "https://apps-apis.google.com/a/feeds/migration/2.0/default/mail/batch";
		const	GOOGLEBATCH_MAXMSG= 20;        
		
		private $_googleclient; 
		private $_msgs;
		private $_totalsize;
		private $_alreadysentchecker;
		private $_uploadenabled;
		private $_uploadtag;
		private $_logger;

		public function __construct()
		{
			$this->_uploadenabled = false;
			$this->_uploadtag = "test_upload";
			$this->reset();
		}
		
		public function setLogger(Zend_Log $log)
		{
			$this->_logger = $log;
		}
		
		/**
		 * Pass the users credentials to Google and obtain a security token 
		 * 
		 * @param string $account	Google account (user@example.com)
		 * @param string $password	Google password
		 */
		
		public function setAccount($account,$password)
		{
		   try 
		   {
				$this->_googleclient = Zend_Gdata_ClientLogin::getHttpClient($account, $password, 'apps');
				$this->_logger->log('Account authorized',Zend_Log::INFO);				
		   } 
		   catch (Zend_Gdata_App_AuthException $e) 
		   {
   				$this->_logger->log('Error: ' . $e->getMessage(),Zend_Log::EMERG);
   				die();
		   }
		}		
		
		/**
		 * If set to true e-mail messages are actually uploaded to google
		 * 
		 *  @param bool $enabled set to true if you want to enable uploading
		 */
		
		public function setUploadEnabled($enabled)
		{
			if (is_bool($enabled))
				$this->_uploadenabled = $enabled;
			else
				throw new Exception("Boolean expected");
		}
		
		/**
		 * Each upload is tagged by default with the "test_upload" label which
		 * makes it easy to manipulate all the uploaded messages in one go when 
		 * inside GMail, for example if you would like to delete the complete set.
		 * 
		 * If set to an empty string no label is attached to each uploaded e-mail. 
		 * 
		 * @param string $label 
		 */
		
		public function setUploadLabel($label)
		{
			if (is_string($label))
				$this->_uploadlabel = $label;
			else
				throw new Exception("String expected");
		}
		
		/** 
		 * Set a previously initialized AlreadySentCheck instance
		 * 
		 * @param $checker
		 */
		
		public function setAlreadySentChecker(AlreadySentCheck $checker)
		{
			$this->_alreadysentchecker = $checker;
		}
		
		/**
		 * Verifies if a message is too large for transmission
		 * 
		 * @return bool  true if the message cannot be uploaded
		 */
		
		public function msgTooLarge($msgSize)
		{
			return $msgSize > BatchUploader::GOOGLEBATCH_LIMIT; 
		}
		
		public function reset()
		{
			$this->_msgs = array();
			$this->_totalsize = 0; 
		}
				
		public function flush()
		{
			// If there is no data, then there is equally no point to contact google
			if (count($this->_msgs)==0)
			{
				$this->_logger->log("Flush but no new messages found, skipping upload",Zend_Log::INFO);				
			 	return;
			}
			
			// Send the combined messages to Google
			if ($this->_uploadenabled)
			try
			{
				$this->_logger->log("Contacting Google, sending " . count($this->_msgs) . " msgs / {$this->_totalsize} bytes",Zend_Log::INFO);
				$gdata = new Zend_Gdata_Gbase($this->_googleclient);
				$gdata->post($this->createxml(), BatchUploader::GOOGLEBATCH_URL);
				unset($gdata);
			}
			catch (Zend_Gdata_App_HttpException $e)
			{
				$this->_logger->log('Error: Problem authenticating: ' . $e->getMessage(),Zend_Log::EMERG);
				die();
			}
			else
				$this->_logger->log('Skipping Google upload of ' . count($this->_msgs) . " msgs / {$this->_totalsize} bytes",Zend_Log::INFO);
			
			// TODO: Go over the response report and see which messages have been
			//       imported succesfully 
			
			// Now the messages have been uploaded we need to flag them as "completed"
			$this->flagCompleted();
			$this->reset();
		}
		
		public function add(&$msg,$label,$id)
		{
			$msgsize = strlen($msg);
			
			// If this message is over the limit , we skip it...
			if ($this->msgTooLarge($msgsize))
			{
				$this->_logger->log("Warning: Skipping large message {$id} ({$msgsize} byes)",Zend_Log::INFO);				
				return;
			}

			// If this message pushes us over the limit flush first  
			if ($msgsize + $this->_totalsize >= BatchUploader::GOOGLEBATCH_LIMIT )
			 $this->flush();

			// We limit the number of messages sent in a single batch
			if (count($this->_msgs) >= BatchUploader::GOOGLEBATCH_MAXMSG)
  			 $this->flush(); 
			 
			// Add the message to memory 
			$this->_msgs[] = array($msg,$label,$id); 
			$this->_totalsize += $msgsize;
			
		}
		
		/**
		 * After a succesful upload all messages in the batch are added to the 
		 * 'alreadysentcheck' class
		 * 
		 */
		
		private function flagCompleted()
		{
			foreach($this->_msgs as $offset => $msgdetails)
			{
				list($msg,$label,$id) = $msgdetails;
				$this->_alreadysentchecker->addEntry($id);
			}
		} 
		
		/**
		 * Create an XML string containing all the messages stored in the buffer 
		 * 
		 * See also: http://code.google.com/apis/apps/email_migration/developers_guide_protocol.html
		 * 
		 * @return string XML formatted to Google Spec
		 */

		private function createxml()
		{
			$batch = $this->xmlheader();

			foreach($this->_msgs as $offset => $msgdetails)
			{
				list($msg,$label,$id) = $msgdetails;
				$batch .= $this->xmlentry($id,$msg,$label); 
			}
			
			$batch .= $this->xmlfooter();
			
			return $batch;
		}
		
		private function xmlheader()
		{
			return '<?xml version="1.0" encoding="utf-8" ?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:batch="http://schemas.google.com/gdata/batch" xmlns:gd="http://schemas.google.com/g/2005">';
		}
		
		private function xmlfooter()
		{
			return "</feed>";
		}
		
		private function xmlentry($batchid,$msg,$label)
		{
			$entry = '<entry>
    <category term="http://schemas.google.com/apps/2006#mailItem"
      scheme="http://schemas.google.com/g/2005#kind" />
    <apps:rfc822Msg encoding="base64" xmlns:apps="http://schemas.google.com/apps/2006">' . "\n\r";
			
			// Encode the message in base64 to avoid a billion possible 
			// problems
			$entry .= base64_encode($msg) 
					  . "\n\r" . "</apps:rfc822Msg>";
			$entry .= '<apps:label labelName="' 
				      . $label  
				      . '" xmlns:apps="http://schemas.google.com/apps/2006" />';
			
			// Add a tag marking this upload
			if ($this->_uploadtag != "")
			{	      
				$entry .= '<apps:label labelName="' 
					   . $this->_uploadtag
				       . '" xmlns:apps="http://schemas.google.com/apps/2006" />';
			}
			
			$entry .= '<batch:id>' . $batchid . '</batch:id>';  
			$entry .= '</entry>';
			
			return $entry;
		}
		
	}


?>
